@coreplane/switchboard 1.262.3 → 1.263.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/config/config.example.yaml +14 -0
- package/dist/assets/deploy/cloudflare/prImages.ts +113 -0
- package/dist/assets/deploy/cloudflare/worker.ts +7 -0
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +1 -1
- package/dist/assets/src/artifacts/prImages.ts +51 -0
- package/dist/assets/src/core/provider.ts +15 -3
- package/dist/cli.js +249 -48
- package/package.json +1 -1
|
@@ -73,6 +73,20 @@ providers:
|
|
|
73
73
|
# # object text contract, or both (absent assumes both). The intake gate
|
|
74
74
|
# # refuses at load a card declaring neither under its classify default.
|
|
75
75
|
# answers: ["tool", "text"]
|
|
76
|
+
# # When published prices change above an input-token threshold, give
|
|
77
|
+
# # all four base and tier rates in USD per million tokens. The whole
|
|
78
|
+
# # request uses the tier above the threshold, including cached input.
|
|
79
|
+
# price:
|
|
80
|
+
# input: 2
|
|
81
|
+
# output: 10
|
|
82
|
+
# cacheRead: 0.2
|
|
83
|
+
# cacheWrite: 2.5
|
|
84
|
+
# tiers:
|
|
85
|
+
# - inputTokensAbove: 272000
|
|
86
|
+
# input: 4
|
|
87
|
+
# output: 15
|
|
88
|
+
# cacheRead: 0.4
|
|
89
|
+
# cacheWrite: 5
|
|
76
90
|
# A passthrough is merged into the request body on the wires whose adapter
|
|
77
91
|
# takes one — a vendor-only feature such as OpenRouter's routing, never a
|
|
78
92
|
# control: it is not decided, not noted and not in the matrix, and a wrong
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { MAX_PR_IMAGE_BYTES, prImageKey, readPrImage, validPrImageSource } from "../../src/artifacts/prImages.ts";
|
|
2
|
+
import { DAY_MS } from "../../src/core/budgets.ts";
|
|
3
|
+
import { systemClock } from "../../src/core/trace/clock.ts";
|
|
4
|
+
import { sameToken } from "./artifactsCopy.ts";
|
|
5
|
+
|
|
6
|
+
/** The small part of R2 needed for deliberate publication. No list or public
|
|
7
|
+
* bucket capability: reads can address only copies this handler marked. */
|
|
8
|
+
export interface PrImageBucket {
|
|
9
|
+
get(key: string): Promise<{
|
|
10
|
+
size: number;
|
|
11
|
+
body: ReadableStream<Uint8Array>;
|
|
12
|
+
httpMetadata?: { contentType?: string };
|
|
13
|
+
customMetadata?: Record<string, string>;
|
|
14
|
+
} | null>;
|
|
15
|
+
put(
|
|
16
|
+
key: string,
|
|
17
|
+
bytes: Uint8Array,
|
|
18
|
+
options: {
|
|
19
|
+
httpMetadata: { contentType: string };
|
|
20
|
+
customMetadata: Record<string, string>;
|
|
21
|
+
},
|
|
22
|
+
): Promise<unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const json = (status: number, body: Record<string, unknown>) =>
|
|
26
|
+
new Response(JSON.stringify(body), {
|
|
27
|
+
status,
|
|
28
|
+
headers: { "content-type": "application/json", "cache-control": "no-store" },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export async function publishPrImage(
|
|
32
|
+
request: Request,
|
|
33
|
+
deps: { bucket?: PrImageBucket; token?: string; clock?: () => number },
|
|
34
|
+
): Promise<Response> {
|
|
35
|
+
const bearer = /^Bearer\s+(\S+)$/.exec(request.headers.get("authorization") ?? "")?.[1];
|
|
36
|
+
if (!deps.token || !bearer || !sameToken(bearer, deps.token))
|
|
37
|
+
return json(401, { error: "publication bearer refused" });
|
|
38
|
+
if (request.method !== "POST") return json(405, { error: "publication requires POST" });
|
|
39
|
+
if (!deps.bucket) return json(503, { error: "artifact store unavailable" });
|
|
40
|
+
let key: unknown;
|
|
41
|
+
let retentionDays: unknown;
|
|
42
|
+
try {
|
|
43
|
+
const input = (await request.json()) as { key?: unknown; retentionDays?: unknown } | null;
|
|
44
|
+
key = input?.key;
|
|
45
|
+
retentionDays = input?.retentionDays;
|
|
46
|
+
} catch {
|
|
47
|
+
/* Refuse below, before reading storage. */
|
|
48
|
+
}
|
|
49
|
+
if (typeof key !== "string" || !validPrImageSource(key))
|
|
50
|
+
return json(400, { error: "publication requires an outbound artifact key" });
|
|
51
|
+
if (typeof retentionDays !== "number" || !Number.isSafeInteger(retentionDays) || retentionDays < 1)
|
|
52
|
+
return json(400, { error: "publication requires retentionDays as a positive integer" });
|
|
53
|
+
const expiresAt = (deps.clock ?? systemClock)() + retentionDays * DAY_MS;
|
|
54
|
+
if (!Number.isSafeInteger(expiresAt) || expiresAt < 1)
|
|
55
|
+
return json(400, { error: "publication expiry is out of range" });
|
|
56
|
+
const object = await deps.bucket.get(key);
|
|
57
|
+
if (!object) return json(404, { error: "artifact not found" });
|
|
58
|
+
let bytes: Uint8Array;
|
|
59
|
+
try {
|
|
60
|
+
bytes = await readPrImage({ ...object, contentType: object.httpMetadata?.contentType ?? "" });
|
|
61
|
+
} catch {
|
|
62
|
+
// A storage stream can fail with internal paths or stack details; the caller
|
|
63
|
+
// needs only the stable validation result.
|
|
64
|
+
return json(400, { error: "invalid PR image" });
|
|
65
|
+
}
|
|
66
|
+
const path = `/pr-images/${crypto.randomUUID()}.png`;
|
|
67
|
+
await deps.bucket.put(prImageKey(path)!, bytes, {
|
|
68
|
+
httpMetadata: { contentType: "image/png" },
|
|
69
|
+
customMetadata: { publishedForPr: "true", sourceKey: key, expiresAt: String(expiresAt) },
|
|
70
|
+
});
|
|
71
|
+
return json(200, { path });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Public only by deliberate copy. The URL is an address, not a run-reader
|
|
75
|
+
* capability; there is no way to substitute a private key into this route. */
|
|
76
|
+
export async function handlePrImage(
|
|
77
|
+
request: Request,
|
|
78
|
+
bucket?: PrImageBucket,
|
|
79
|
+
clock: () => number = systemClock,
|
|
80
|
+
): Promise<Response> {
|
|
81
|
+
if (request.method !== "GET" && request.method !== "HEAD") return json(405, { error: "GET or HEAD only" });
|
|
82
|
+
const url = new URL(request.url);
|
|
83
|
+
const key = prImageKey(url.pathname);
|
|
84
|
+
if (!key || url.search || !bucket) return json(404, { error: "not found" });
|
|
85
|
+
const object = await bucket.get(key);
|
|
86
|
+
// Lifecycle deletion is asynchronous; it must not extend public availability.
|
|
87
|
+
// Older or malformed copies without a valid deadline also fail closed.
|
|
88
|
+
const expiresAt = Number(object?.customMetadata?.expiresAt);
|
|
89
|
+
if (
|
|
90
|
+
!object ||
|
|
91
|
+
object.customMetadata?.publishedForPr !== "true" ||
|
|
92
|
+
!Number.isSafeInteger(expiresAt) ||
|
|
93
|
+
expiresAt < 1 ||
|
|
94
|
+
clock() >= expiresAt ||
|
|
95
|
+
object.httpMetadata?.contentType !== "image/png" ||
|
|
96
|
+
object.size < 1 ||
|
|
97
|
+
object.size > MAX_PR_IMAGE_BYTES
|
|
98
|
+
) {
|
|
99
|
+
await object?.body.cancel();
|
|
100
|
+
return json(404, { error: "not found" });
|
|
101
|
+
}
|
|
102
|
+
if (request.method === "HEAD") await object.body.cancel();
|
|
103
|
+
return new Response(request.method === "HEAD" ? null : object.body, {
|
|
104
|
+
headers: {
|
|
105
|
+
"content-type": "image/png",
|
|
106
|
+
"content-length": String(object.size),
|
|
107
|
+
"content-disposition": 'inline; filename="screenshot.png"',
|
|
108
|
+
"cache-control": "no-store",
|
|
109
|
+
"x-content-type-options": "nosniff",
|
|
110
|
+
"content-security-policy": "default-src 'none'; sandbox",
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -46,6 +46,8 @@ import { systemClock } from "../../src/core/trace/clock.ts";
|
|
|
46
46
|
import { createTracer } from "../../src/core/trace/tracer.ts";
|
|
47
47
|
import { shimRoute, stripTraceContext, withTraceContext, workerLogSink } from "../../src/core/trace/workerTrace.ts";
|
|
48
48
|
import { COPY_PATH, handleArtifactsCopy } from "./artifactsCopy.ts";
|
|
49
|
+
import { handlePrImage, publishPrImage } from "./prImages.ts";
|
|
50
|
+
import { PR_IMAGE_PUBLISH_PATH } from "../../src/artifacts/prImages.ts";
|
|
49
51
|
import { withKnownLength } from "./knownLength.ts";
|
|
50
52
|
import type { ShipCoordinatorParams } from "./coordinator";
|
|
51
53
|
import { INSTANCE, INTERNAL } from "./shared";
|
|
@@ -456,6 +458,11 @@ const withLength = (res: Response): Response => withKnownLength(res, (size) => n
|
|
|
456
458
|
export default {
|
|
457
459
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
458
460
|
const pathname = new URL(request.url).pathname;
|
|
461
|
+
// Only deliberately published copies are public. The existing run reader
|
|
462
|
+
// and the bucket stay private; the write endpoint checks its own bearer.
|
|
463
|
+
if (pathname.startsWith("/pr-images/")) return withLength(await handlePrImage(request, env.ARTIFACTS));
|
|
464
|
+
if (pathname === PR_IMAGE_PUBLISH_PATH)
|
|
465
|
+
return publishPrImage(request, { bucket: env.ARTIFACTS, token: env.ARTIFACTS_COPY_TOKEN });
|
|
459
466
|
// The public edge (docs/reference/specs/tracing.md item 22): whatever trace context the
|
|
460
467
|
// caller sent is stripped, and what the container sees carries this
|
|
461
468
|
// Worker's own root. A static asset or the live view's SSE stream gets no
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.263.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.263.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -21060,7 +21060,7 @@
|
|
|
21060
21060
|
},
|
|
21061
21061
|
"packages/switchboard": {
|
|
21062
21062
|
"name": "@coreplane/switchboard",
|
|
21063
|
-
"version": "1.
|
|
21063
|
+
"version": "1.263.0",
|
|
21064
21064
|
"license": "Apache-2.0",
|
|
21065
21065
|
"dependencies": {
|
|
21066
21066
|
"@earendil-works/pi-ai": "0.85.1",
|
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.263.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/dist/assets/source.json
CHANGED
|
@@ -216,7 +216,7 @@ const RESIDENT_TOOLCHAIN = `The resident image carries ${IMAGE_TOOLCHAIN}; plus
|
|
|
216
216
|
// item 10): the one way a run's screenshot reaches the person. Said once so
|
|
217
217
|
// the sandbox and resident variants cannot drift on it.
|
|
218
218
|
const SHOW_FILES = `Files the person should SEE go through the attach_file tool: a screenshot from \`playwright screenshot\`, a rendered PDF, a recording — it posts the workspace file into this conversation, where an image renders inline. Use it whenever you produce an image worth showing (a visual change, a rendered page, a before/after); a link to a file on GitHub is not a picture.
|
|
219
|
-
SCREENSHOTS GO TO BOTH PLACES, ALL OF THEM: when the request asks for screenshots, or the change is visual, every capture is attached here with attach_file AND published on the pull request —
|
|
219
|
+
SCREENSHOTS GO TO BOTH PLACES, ALL OF THEM: when the request asks for screenshots, or the change is visual, every capture is attached here with attach_file AND published on the pull request — use PNG captures, inspect/redact sensitive content, and call attach_file with publishForPr: true to deliberately make a public copy after thread delivery. Embed the returned public URL with Markdown image syntax in the description's validation section or a PR comment, never as a bare link. This applies unless the request names one destination; for thread-only delivery omit publishForPr. Never attach a subset and link the rest. Only deliberately published PNGs (at most 10 MiB) are public, for the artifact retention window (default 30 days), not forever. Never push an assets branch, put captures in the PR's diff, or expose a tokened run-artifact URL as a workaround. If publication is unavailable or refused, report that limit; if attachment was refused too, still attach privately with publishForPr omitted. Do not claim the PR renders an image.
|
|
220
220
|
Whole files: up to 1 GiB where the artifact store is configured (a recording, a large PDF), 10 MiB otherwise — the tool's result says which applies; over the limit, link to the file.
|
|
221
221
|
Files the person dropped on the thread that were too large to show you inline are already in ./attachments/ in your workspace when the turn's text names them (a video for ffmpeg, a large PDF, a zip); read them from there — never ask for a re-upload.
|
|
222
222
|
Text stays in your message; do not attach what you can say.`;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Deliberate public copies, not a public bucket or an exception to the run reader.
|
|
2
|
+
export const PR_IMAGE_PUBLISH_PATH = "/artifacts/publish-pr-image";
|
|
3
|
+
export const MAX_PR_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
4
|
+
const UUID = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
|
|
5
|
+
const PUBLIC_PATH = new RegExp(`^/pr-images/(${UUID})\\.png$`);
|
|
6
|
+
|
|
7
|
+
export function validPrImagePath(path: string): boolean {
|
|
8
|
+
return PUBLIC_PATH.test(path);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function prImageKey(path: string): string | undefined {
|
|
12
|
+
const match = PUBLIC_PATH.exec(path);
|
|
13
|
+
return match ? `published-pr/${match[1]}.png` : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function validPrImageSource(key: string): boolean {
|
|
17
|
+
return /^runs\/[A-Za-z0-9_-]+\/out\/\d+-[A-Za-z0-9._-]+$/.test(key);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Bounded to one small screenshot in the Worker, never in the bot process.
|
|
21
|
+
* Count the stream as well as its metadata: a dishonest length must not
|
|
22
|
+
* turn the public endpoint into an unbounded read or an active-content host. */
|
|
23
|
+
export async function readPrImage(object: {
|
|
24
|
+
size: number;
|
|
25
|
+
contentType: string;
|
|
26
|
+
body: ReadableStream<Uint8Array>;
|
|
27
|
+
}): Promise<Uint8Array> {
|
|
28
|
+
const reader = object.body.getReader();
|
|
29
|
+
try {
|
|
30
|
+
if (object.contentType !== "image/png") throw new Error("PR images require PNG content type");
|
|
31
|
+
if (!Number.isInteger(object.size) || object.size < 1 || object.size > MAX_PR_IMAGE_BYTES) {
|
|
32
|
+
throw new Error(`PR image size must be 1..${MAX_PR_IMAGE_BYTES} bytes`);
|
|
33
|
+
}
|
|
34
|
+
const bytes = new Uint8Array(object.size);
|
|
35
|
+
let used = 0;
|
|
36
|
+
for (;;) {
|
|
37
|
+
const { done, value } = await reader.read();
|
|
38
|
+
if (done) break;
|
|
39
|
+
if (used + value.length > bytes.length) throw new Error("PR image length exceeds its declared size");
|
|
40
|
+
bytes.set(value, used);
|
|
41
|
+
used += value.length;
|
|
42
|
+
}
|
|
43
|
+
if (used !== bytes.length) throw new Error("PR image length differs from its declared size");
|
|
44
|
+
const signature = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
45
|
+
if (!signature.every((byte, index) => bytes[index] === byte)) throw new Error("PR images require a PNG signature");
|
|
46
|
+
return bytes;
|
|
47
|
+
} finally {
|
|
48
|
+
await reader.cancel().catch(() => {});
|
|
49
|
+
reader.releaseLock();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -446,7 +446,7 @@ export function classifyProviderFailure(input: ProviderFailureAnswer): ProviderF
|
|
|
446
446
|
});
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
-
function typedProviderFailureOf(error: unknown, seen = new Set<object>()): ProviderFailure | undefined {
|
|
449
|
+
export function typedProviderFailureOf(error: unknown, seen = new Set<object>()): ProviderFailure | undefined {
|
|
450
450
|
if (error instanceof ProviderFailure) return error;
|
|
451
451
|
const row = providerFailureRecord(error);
|
|
452
452
|
if (!row || seen.has(row)) return undefined;
|
|
@@ -505,8 +505,20 @@ export interface ProviderModelOverride {
|
|
|
505
505
|
inputs?: { image?: boolean; document?: boolean };
|
|
506
506
|
/** The model's cache rule. */
|
|
507
507
|
cache?: "automatic" | "markers" | "none" | "unknown";
|
|
508
|
-
/** USD per million tokens, by kind. */
|
|
509
|
-
price?: {
|
|
508
|
+
/** USD per million tokens, by kind, with whole-request long-context rates. */
|
|
509
|
+
price?: {
|
|
510
|
+
input?: number;
|
|
511
|
+
output?: number;
|
|
512
|
+
cacheRead?: number;
|
|
513
|
+
cacheWrite?: number;
|
|
514
|
+
tiers?: Array<{
|
|
515
|
+
inputTokensAbove: number;
|
|
516
|
+
input: number;
|
|
517
|
+
output: number;
|
|
518
|
+
cacheRead: number;
|
|
519
|
+
cacheWrite: number;
|
|
520
|
+
}>;
|
|
521
|
+
};
|
|
510
522
|
/** The answer shapes the model can produce for a forced one-call turn (the
|
|
511
523
|
* router's, intake's): `tool` — a forced tool call — and `text` — the
|
|
512
524
|
* one-JSON-object text contract. Absent, both are assumed; an empty list
|
package/dist/cli.js
CHANGED
|
@@ -362,6 +362,24 @@ var init_clock = __esm({
|
|
|
362
362
|
}
|
|
363
363
|
});
|
|
364
364
|
|
|
365
|
+
// ../../src/artifacts/prImages.ts
|
|
366
|
+
function validPrImagePath(path) {
|
|
367
|
+
return PUBLIC_PATH.test(path);
|
|
368
|
+
}
|
|
369
|
+
function validPrImageSource(key) {
|
|
370
|
+
return /^runs\/[A-Za-z0-9_-]+\/out\/\d+-[A-Za-z0-9._-]+$/.test(key);
|
|
371
|
+
}
|
|
372
|
+
var PR_IMAGE_PUBLISH_PATH, MAX_PR_IMAGE_BYTES, UUID, PUBLIC_PATH;
|
|
373
|
+
var init_prImages = __esm({
|
|
374
|
+
"../../src/artifacts/prImages.ts"() {
|
|
375
|
+
"use strict";
|
|
376
|
+
PR_IMAGE_PUBLISH_PATH = "/artifacts/publish-pr-image";
|
|
377
|
+
MAX_PR_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
378
|
+
UUID = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
|
|
379
|
+
PUBLIC_PATH = new RegExp(`^/pr-images/(${UUID})\\.png$`);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
365
383
|
// ../../src/artifacts/store.ts
|
|
366
384
|
import { AwsClient } from "aws4fetch";
|
|
367
385
|
function parseByteRange(header4) {
|
|
@@ -392,6 +410,7 @@ var init_store = __esm({
|
|
|
392
410
|
"use strict";
|
|
393
411
|
init_clock();
|
|
394
412
|
init_config();
|
|
413
|
+
init_prImages();
|
|
395
414
|
PRESIGN_TTL_SECONDS = ARTIFACT_DEFAULTS.presignTtlSeconds;
|
|
396
415
|
IDENTITY = { "accept-encoding": "identity" };
|
|
397
416
|
R2ArtifactStore = class {
|
|
@@ -400,6 +419,7 @@ var init_store = __esm({
|
|
|
400
419
|
endpoint;
|
|
401
420
|
fetchImpl;
|
|
402
421
|
clock;
|
|
422
|
+
retentionDays;
|
|
403
423
|
copy;
|
|
404
424
|
constructor(opts) {
|
|
405
425
|
this.bucket = opts.bucket;
|
|
@@ -413,6 +433,7 @@ var init_store = __esm({
|
|
|
413
433
|
});
|
|
414
434
|
this.fetchImpl = opts.fetch ?? fetch;
|
|
415
435
|
this.clock = opts.clock ?? systemClock;
|
|
436
|
+
this.retentionDays = opts.retentionDays ?? ARTIFACT_DEFAULTS.retentionDays;
|
|
416
437
|
this.copy = { ...opts.copy, timeoutMs: opts.copy.timeoutMs ?? ARTIFACT_DEFAULTS.copyTimeoutMs };
|
|
417
438
|
}
|
|
418
439
|
objectUrl(key) {
|
|
@@ -472,6 +493,26 @@ var init_store = __esm({
|
|
|
472
493
|
if (!Number.isFinite(size) || size < 0) throw new Error(`artifact store: GET ${key} answered without a length`);
|
|
473
494
|
return { size, contentType, body: res.body };
|
|
474
495
|
}
|
|
496
|
+
async publishPrImage(key) {
|
|
497
|
+
if (!validPrImageSource(key)) throw new Error("PR publication requires an outbound artifact key");
|
|
498
|
+
const base = new URL(this.copy.baseUrl);
|
|
499
|
+
if (base.protocol !== "https:" || base.username || base.password || base.search || base.hash || base.pathname !== "/") {
|
|
500
|
+
throw new Error("PR publication requires a credential-free HTTPS origin");
|
|
501
|
+
}
|
|
502
|
+
const res = await this.fetchImpl(`${base.origin}${PR_IMAGE_PUBLISH_PATH}`, {
|
|
503
|
+
method: "POST",
|
|
504
|
+
headers: { authorization: `Bearer ${this.copy.token.reveal()}`, "content-type": "application/json" },
|
|
505
|
+
body: JSON.stringify({ key, retentionDays: this.retentionDays }),
|
|
506
|
+
redirect: "error",
|
|
507
|
+
signal: AbortSignal.timeout(this.copy.timeoutMs)
|
|
508
|
+
});
|
|
509
|
+
if (!res.ok) throw new Error(`PR publication answered HTTP ${res.status}`);
|
|
510
|
+
const answer = await res.json();
|
|
511
|
+
if (typeof answer.path !== "string" || !validPrImagePath(answer.path)) {
|
|
512
|
+
throw new Error("PR publication returned an invalid public path");
|
|
513
|
+
}
|
|
514
|
+
return `${base.origin}${answer.path}`;
|
|
515
|
+
}
|
|
475
516
|
async copyFromUrl(input) {
|
|
476
517
|
const res = await this.fetchImpl(`${this.copy.baseUrl.replace(/\/$/, "")}/artifacts/copy`, {
|
|
477
518
|
method: "POST",
|
|
@@ -509,6 +550,7 @@ function buildArtifactStore(cfg, secrets, opts) {
|
|
|
509
550
|
return new R2ArtifactStore({
|
|
510
551
|
accountId: cfg.r2.accountId,
|
|
511
552
|
bucket: cfg.r2.bucket,
|
|
553
|
+
retentionDays: cfg.retentionDays,
|
|
512
554
|
accessKeyId: secrets.get("ARTIFACTS_R2_ACCESS_KEY_ID"),
|
|
513
555
|
secretAccessKey: secrets.get("ARTIFACTS_R2_SECRET_ACCESS_KEY"),
|
|
514
556
|
copy: {
|
|
@@ -2335,7 +2377,7 @@ EVERY PR includes one that already exists when you push \u2014 opened by a perso
|
|
|
2335
2377
|
SANDBOX_TOOLCHAIN = `The sandbox image carries ${IMAGE_TOOLCHAIN}; and Docker (the engine starts on the first \`docker\` call).`;
|
|
2336
2378
|
RESIDENT_TOOLCHAIN = `The resident image carries ${IMAGE_TOOLCHAIN}; plus yarn and bun \u2014 and no Docker.`;
|
|
2337
2379
|
SHOW_FILES = `Files the person should SEE go through the attach_file tool: a screenshot from \`playwright screenshot\`, a rendered PDF, a recording \u2014 it posts the workspace file into this conversation, where an image renders inline. Use it whenever you produce an image worth showing (a visual change, a rendered page, a before/after); a link to a file on GitHub is not a picture.
|
|
2338
|
-
SCREENSHOTS GO TO BOTH PLACES, ALL OF THEM: when the request asks for screenshots, or the change is visual, every capture is attached here with attach_file AND published on the pull request \u2014
|
|
2380
|
+
SCREENSHOTS GO TO BOTH PLACES, ALL OF THEM: when the request asks for screenshots, or the change is visual, every capture is attached here with attach_file AND published on the pull request \u2014 use PNG captures, inspect/redact sensitive content, and call attach_file with publishForPr: true to deliberately make a public copy after thread delivery. Embed the returned public URL with Markdown image syntax in the description's validation section or a PR comment, never as a bare link. This applies unless the request names one destination; for thread-only delivery omit publishForPr. Never attach a subset and link the rest. Only deliberately published PNGs (at most 10 MiB) are public, for the artifact retention window (default 30 days), not forever. Never push an assets branch, put captures in the PR's diff, or expose a tokened run-artifact URL as a workaround. If publication is unavailable or refused, report that limit; if attachment was refused too, still attach privately with publishForPr omitted. Do not claim the PR renders an image.
|
|
2339
2381
|
Whole files: up to 1 GiB where the artifact store is configured (a recording, a large PDF), 10 MiB otherwise \u2014 the tool's result says which applies; over the limit, link to the file.
|
|
2340
2382
|
Files the person dropped on the thread that were too large to show you inline are already in ./attachments/ in your workspace when the turn's text names them (a video for ffmpeg, a large PDF, a zip); read them from there \u2014 never ask for a re-upload.
|
|
2341
2383
|
Text stays in your message; do not attach what you can say.`;
|
|
@@ -7908,12 +7950,42 @@ function validateModelOverride(path, raw) {
|
|
|
7908
7950
|
if (m.price !== void 0) {
|
|
7909
7951
|
if (typeof m.price !== "object" || m.price === null || Array.isArray(m.price))
|
|
7910
7952
|
throw new Error(`${path}.price must be a mapping of kind \u2192 USD per million tokens`);
|
|
7911
|
-
|
|
7953
|
+
const price = m.price;
|
|
7954
|
+
for (const [kind, v] of Object.entries(price)) {
|
|
7955
|
+
if (kind === "tiers") continue;
|
|
7912
7956
|
if (!Object.hasOwn(PRICE_KINDS2, kind))
|
|
7913
7957
|
throw new Error(`${path}.price.${kind} is not a known kind (input, output, cacheRead, cacheWrite)`);
|
|
7914
7958
|
if (typeof v !== "number" || !Number.isFinite(v) || v < 0)
|
|
7915
7959
|
throw new Error(`${path}.price.${kind} must be a finite number of USD per million tokens, 0 or more`);
|
|
7916
7960
|
}
|
|
7961
|
+
if (price.tiers !== void 0) {
|
|
7962
|
+
if (!Array.isArray(price.tiers) || price.tiers.length === 0)
|
|
7963
|
+
throw new Error(`${path}.price.tiers must be a nonempty list of long-context rates`);
|
|
7964
|
+
for (const kind of Object.keys(PRICE_KINDS2)) {
|
|
7965
|
+
if (price[kind] === void 0) throw new Error(`${path}.price.${kind} is required with tiers`);
|
|
7966
|
+
}
|
|
7967
|
+
const thresholds = /* @__PURE__ */ new Set();
|
|
7968
|
+
for (const [index, rawTier] of price.tiers.entries()) {
|
|
7969
|
+
const tierPath = `${path}.price.tiers[${index}]`;
|
|
7970
|
+
if (typeof rawTier !== "object" || rawTier === null || Array.isArray(rawTier))
|
|
7971
|
+
throw new Error(`${tierPath} must be a mapping of threshold and rates`);
|
|
7972
|
+
const tier = rawTier;
|
|
7973
|
+
for (const key of Object.keys(tier)) {
|
|
7974
|
+
if (key !== "inputTokensAbove" && !Object.hasOwn(PRICE_KINDS2, key))
|
|
7975
|
+
throw new Error(`${tierPath}.${key} is not a known tier field`);
|
|
7976
|
+
}
|
|
7977
|
+
if (!Number.isInteger(tier.inputTokensAbove) || tier.inputTokensAbove <= 0)
|
|
7978
|
+
throw new Error(`${tierPath}.inputTokensAbove must be a positive integer of tokens`);
|
|
7979
|
+
const threshold = tier.inputTokensAbove;
|
|
7980
|
+
if (thresholds.has(threshold)) throw new Error(`${tierPath}.inputTokensAbove duplicates a previous threshold`);
|
|
7981
|
+
thresholds.add(threshold);
|
|
7982
|
+
for (const kind of Object.keys(PRICE_KINDS2)) {
|
|
7983
|
+
const rate = tier[kind];
|
|
7984
|
+
if (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0)
|
|
7985
|
+
throw new Error(`${tierPath}.${kind} must be a finite number of USD per million tokens, 0 or more`);
|
|
7986
|
+
}
|
|
7987
|
+
}
|
|
7988
|
+
}
|
|
7917
7989
|
}
|
|
7918
7990
|
}
|
|
7919
7991
|
function validateProviders(cfg, source2) {
|
|
@@ -40932,6 +41004,39 @@ function operatorPresets() {
|
|
|
40932
41004
|
}
|
|
40933
41005
|
];
|
|
40934
41006
|
}
|
|
41007
|
+
function requestedModelWord(request2, modelWord, ref) {
|
|
41008
|
+
const word = modelWord.trim().toLowerCase();
|
|
41009
|
+
if (!word) return false;
|
|
41010
|
+
const words = [];
|
|
41011
|
+
let current = "";
|
|
41012
|
+
for (const char of request2.toLowerCase()) {
|
|
41013
|
+
const code = char.charCodeAt(0);
|
|
41014
|
+
if (code >= 97 && code <= 122 || code >= 48 && code <= 57 || char === "-" || char === ".") {
|
|
41015
|
+
current += char;
|
|
41016
|
+
} else if (current) {
|
|
41017
|
+
words.push(current);
|
|
41018
|
+
current = "";
|
|
41019
|
+
}
|
|
41020
|
+
}
|
|
41021
|
+
if (current) words.push(current);
|
|
41022
|
+
const cues = /* @__PURE__ */ new Set(["with", "using", "use", "on", "via", "model"]);
|
|
41023
|
+
let selected = false;
|
|
41024
|
+
for (let i = 0; i < words.length; i++) {
|
|
41025
|
+
if (words[i] !== word) continue;
|
|
41026
|
+
const previous = words[i - 1];
|
|
41027
|
+
if (previous !== void 0 && cues.has(previous)) selected = true;
|
|
41028
|
+
if ((previous === "the" || previous === "a") && i >= 2 && cues.has(words[i - 2])) selected = true;
|
|
41029
|
+
}
|
|
41030
|
+
if (!selected) return false;
|
|
41031
|
+
let modelId;
|
|
41032
|
+
try {
|
|
41033
|
+
modelId = parseModelRef(ref).model.toLowerCase();
|
|
41034
|
+
} catch {
|
|
41035
|
+
return true;
|
|
41036
|
+
}
|
|
41037
|
+
const modelName = modelId.split("/").pop();
|
|
41038
|
+
return modelName === word || modelName?.endsWith(`-${word}`) === true;
|
|
41039
|
+
}
|
|
40935
41040
|
function compareCapability(a, b) {
|
|
40936
41041
|
const machine = Number(a.machine !== "none") - Number(b.machine !== "none");
|
|
40937
41042
|
if (machine !== 0) return machine;
|
|
@@ -40968,6 +41073,8 @@ function ownerNote(owner) {
|
|
|
40968
41073
|
const steer = owner.kind === "live" && owner.runId !== void 0 ? ` bind \`steer run ${owner.runId} <words>\` to deliver it,` : "";
|
|
40969
41074
|
if (owner.kind === "pipeline")
|
|
40970
41075
|
return `This thread is owned by ${who2}: the request below is a continuation of that durable task. Read tools may ground the decision, but every action folds into the owner so the current pull request is re-read and the pipeline resumes; an informational command or question is not fulfillment.`;
|
|
41076
|
+
if (owner.kind === "unit")
|
|
41077
|
+
return `This thread is owned by ${who2}: the request below is a follow-up for that unit. An inferred read command cannot answer it; fold the whole message into the unit unchanged. Ask a question only when a required detail is missing.`;
|
|
40971
41078
|
return `This thread is owned by ${who2}: the request below is a follow-up for that owner. To act on it,${steer} bind a read command, or ask a question. Any other decision \u2014 a refusal, a preset, a write \u2014 folds the whole message into the owner unchanged and posts no answer.`;
|
|
40972
41079
|
}
|
|
40973
41080
|
function operatorProjection(input) {
|
|
@@ -40989,8 +41096,8 @@ function buildOperatorPrompt(input) {
|
|
|
40989
41096
|
] : [],
|
|
40990
41097
|
"A write ask in a named or inherited repository binds the write preset even when a detail inside it is unresolved \u2014 the run it starts resolves the detail with the repository in front of it. Ask a question only for a fork the run itself could not resolve, and a question's proposal must be a line that would do the asked work: a write line for a write ask, never a read (an exploration, a listing, a summary) standing in for the work.",
|
|
40991
41098
|
"A read command answers only a read intent: an ask to change, set, switch or update something is a write, and a listing or a show never answers it. Every command call declares its `intent`. When a write ask misses a required detail, or names a model provider this deployment does not have, read `provider_models` for the refs this deployment can run, then call `ask` with a proposal that would do the write built from them \u2014 the person's yes runs it, and their next words refine it.",
|
|
40992
|
-
"When the request names a model in plain words \u2014 'with astra, \u2026', 'use sol for this', 'on gpt-6' \u2014 read `provider_models` to resolve the word to exactly ONE ref this deployment can run
|
|
40993
|
-
"`bind_preset` runs the preset on the request as the author asked it \u2014 the author's own words ride by reference, so the call names only the preset,
|
|
41099
|
+
"When the request names a model in plain words \u2014 'with astra, \u2026', 'use sol for this', 'on gpt-6' \u2014 read `provider_models` to resolve the word to exactly ONE ref this deployment can run. Pass that ref as `bind_preset`'s `model` and one exact model-name word from the person's request as `modelWord` (such as 'astra', 'o3', or 'gpt-6'): the run then uses it, exactly as a typed `model:` directive would. The request still rides verbatim \u2014 never strip the model word from it. A word that matches several refs, or none, is one `ask` naming the catalogue's candidate refs \u2014 never a guess and never a silent default; a request naming no model omits both `model` and `modelWord`.",
|
|
41100
|
+
"`bind_preset` runs the preset on the request as the author asked it \u2014 the author's own words ride by reference, so the call names only the preset, optional model and modelWord, and the reason: never re-type the request, never a flag form and never a paraphrase.",
|
|
40994
41101
|
"",
|
|
40995
41102
|
// 2. Projection: the presets and commands THIS author may run.
|
|
40996
41103
|
"Presets this author may run:",
|
|
@@ -41052,6 +41159,10 @@ function operatorTools(input) {
|
|
|
41052
41159
|
type: "string",
|
|
41053
41160
|
description: "the model ref the run uses, ONLY when the request names a model in plain words: a `<provider>/<model>` ref `provider_models` lists, resolved from the person's word \u2014 omit when no model is named, and ask instead of guessing when the word matches several refs or none"
|
|
41054
41161
|
},
|
|
41162
|
+
modelWord: {
|
|
41163
|
+
type: "string",
|
|
41164
|
+
description: "one exact model-name word in the person's request that `model` resolves, such as astra, o3, or gpt-6; omit with `model` when no model is named"
|
|
41165
|
+
},
|
|
41055
41166
|
repo: {
|
|
41056
41167
|
type: "string",
|
|
41057
41168
|
pattern: "^[\\w.-]+/[\\w.-]+$",
|
|
@@ -41253,15 +41364,15 @@ function parseOperatorTurn(answer, ctx) {
|
|
|
41253
41364
|
...typeof input.filter === "string" && input.filter.trim().length > 0 ? { filter: input.filter } : {}
|
|
41254
41365
|
};
|
|
41255
41366
|
if (answer.tool === OPERATOR_BIND_TOOL) {
|
|
41256
|
-
const { preset, reason: reason2, model, repo } = input;
|
|
41367
|
+
const { preset, reason: reason2, model, modelWord, repo } = input;
|
|
41257
41368
|
if (typeof preset !== "string" || !ctx.presets.includes(preset))
|
|
41258
41369
|
return {
|
|
41259
41370
|
kind: "violation",
|
|
41260
41371
|
violation: `bind_preset named "${String(preset)}", not a preset the projection offers`
|
|
41261
41372
|
};
|
|
41262
41373
|
let ref;
|
|
41263
|
-
if (model !==
|
|
41264
|
-
const trimmed2 =
|
|
41374
|
+
if (typeof model === "string" && model.trim() !== "" && typeof modelWord === "string" && requestedModelWord(ctx.requestText, modelWord, model)) {
|
|
41375
|
+
const trimmed2 = model.trim();
|
|
41265
41376
|
const parsed = /^([A-Za-z0-9_.-]+)\/\S+$/.exec(trimmed2);
|
|
41266
41377
|
if (parsed === null)
|
|
41267
41378
|
return {
|
|
@@ -41373,7 +41484,7 @@ function ownedDecisionRuns(event, owner, commands) {
|
|
|
41373
41484
|
const def = commands.list().find((c) => c.id === parsed.id);
|
|
41374
41485
|
if (!def) return false;
|
|
41375
41486
|
if (def.id === "steer.run") return !(owner.kind === "live" && owner.runId === void 0);
|
|
41376
|
-
return boundBlastRadius(def, parsed.input) === "read";
|
|
41487
|
+
return owner.kind !== "unit" && boundBlastRadius(def, parsed.input) === "read";
|
|
41377
41488
|
});
|
|
41378
41489
|
}
|
|
41379
41490
|
function isYesAnswer(text) {
|
|
@@ -41472,6 +41583,16 @@ async function runOperator(input, model, opts = {}) {
|
|
|
41472
41583
|
const carried = attemptsOfThrow(err2);
|
|
41473
41584
|
if (carried) attempts.push(...carried);
|
|
41474
41585
|
const failure2 = providerFailureOf(err2);
|
|
41586
|
+
const typedFailure = typedProviderFailureOf(err2);
|
|
41587
|
+
const localAbort = err2 instanceof Error && err2.name === "AbortError";
|
|
41588
|
+
if (signal2.aborted && typedFailure === void 0 && (localAbort || failure2.cause === "permanent" && failure2.status === void 0)) {
|
|
41589
|
+
return answered({
|
|
41590
|
+
kind: "refusal",
|
|
41591
|
+
cause: "timeout",
|
|
41592
|
+
reason: "operator_timeout",
|
|
41593
|
+
text: "The routing request timed out; nothing started."
|
|
41594
|
+
});
|
|
41595
|
+
}
|
|
41475
41596
|
const rejected = failure2.cause === "request-rejected" && failure2.status === 400 ? failure2.schemaRejection : void 0;
|
|
41476
41597
|
const offered = rejected !== void 0 && [prompt.tool, ...prompt.tools ?? []].some((candidate) => candidate.name === rejected.tool);
|
|
41477
41598
|
const retry = offered && rejected !== void 0 && schemaReasks < OPERATOR_SCHEMA_REASKS_MAX ? promptWithoutTool(prompt, rejected.tool) : void 0;
|
|
@@ -41712,9 +41833,11 @@ async function executeOperatorDecision(deps, ctx) {
|
|
|
41712
41833
|
await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
|
|
41713
41834
|
return answered;
|
|
41714
41835
|
}
|
|
41715
|
-
if (event.providerFailure !== void 0) {
|
|
41836
|
+
if (event.providerFailure !== void 0 || event.refusalCause === "timeout") {
|
|
41716
41837
|
io.requestFailed?.();
|
|
41717
|
-
await io.reply(
|
|
41838
|
+
await io.reply(
|
|
41839
|
+
event.refusalText ?? (event.providerFailure !== void 0 ? renderProviderFailure(event.providerFailure, "ended") : "The routing request timed out; nothing started.")
|
|
41840
|
+
);
|
|
41718
41841
|
await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
|
|
41719
41842
|
return answered;
|
|
41720
41843
|
}
|
|
@@ -41858,7 +41981,7 @@ var init_operator = __esm({
|
|
|
41858
41981
|
};
|
|
41859
41982
|
OPERATOR_READS_MAX = 4;
|
|
41860
41983
|
OPERATOR_SCHEMA_REASKS_MAX = 4;
|
|
41861
|
-
OPERATOR_TIMEOUT_MS =
|
|
41984
|
+
OPERATOR_TIMEOUT_MS = MINUTE_MS;
|
|
41862
41985
|
OPERATOR_QUESTION_MARKER = "Did you mean:";
|
|
41863
41986
|
OPERATOR_REQUEST_CAP = 600;
|
|
41864
41987
|
OPERATOR_SOURCES_MAX = 12;
|
|
@@ -43117,7 +43240,7 @@ function parsePutReport(output) {
|
|
|
43117
43240
|
function postCommandFor(path, url) {
|
|
43118
43241
|
return `curl -fsS --upload-file ${shellQuote(path)} -X POST ${shellQuote(url)}`;
|
|
43119
43242
|
}
|
|
43120
|
-
async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
43243
|
+
async function attachThroughStore(path, name, lead, artifacts, ctx, publishForPr) {
|
|
43121
43244
|
const exec = (command2, timeoutMs2) => {
|
|
43122
43245
|
const opts = { timeoutMs: timeoutMs2, ...ctx.signal ? { signal: ctx.signal } : {} };
|
|
43123
43246
|
return ctx.executor.exec(command2, opts);
|
|
@@ -43129,6 +43252,7 @@ async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
|
43129
43252
|
const size = parseByteSize(statOut);
|
|
43130
43253
|
if (size === null) return `error: could not measure ${path}: ${statOut.trim()}`;
|
|
43131
43254
|
if (size === 0) return `error: ${path} is empty \u2014 nothing to attach`;
|
|
43255
|
+
if (publishForPr && size > MAX_PR_IMAGE_BYTES) return `error: PR images take at most ${MAX_PR_IMAGE_BYTES} bytes`;
|
|
43132
43256
|
if (size > MAX_ARTIFACT_BYTES) {
|
|
43133
43257
|
return `error: ${path} is ${size} bytes; attach_file takes files up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) \u2014 link to the file instead`;
|
|
43134
43258
|
}
|
|
@@ -43159,12 +43283,22 @@ async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
|
43159
43283
|
return `error: the artifact store holds ${head.size} bytes for ${name}, not the ${size} measured${curlSaid} \u2014 nothing was posted`;
|
|
43160
43284
|
}
|
|
43161
43285
|
ctx.publish?.({ type: "artifact", direction: "out", key, name, size, contentType, callId });
|
|
43286
|
+
const finish2 = async (attached) => {
|
|
43287
|
+
if (!publishForPr) return attached;
|
|
43288
|
+
try {
|
|
43289
|
+
const url = await artifacts.store.publishPrImage(key);
|
|
43290
|
+
return `${attached}; public PR image (retained by the artifact lifecycle): `;
|
|
43291
|
+
} catch (err2) {
|
|
43292
|
+
return `error: ${attached}; PR publication failed: ${describe4(err2)} \u2014 no public PR image was returned`;
|
|
43293
|
+
}
|
|
43294
|
+
};
|
|
43162
43295
|
if (!ctx.uploadTicket) {
|
|
43163
43296
|
const link = artifacts.artifactUrl?.(key);
|
|
43164
43297
|
const where = link ? ` \u2014 ${link}` : ` is on the run page as ${key}`;
|
|
43165
43298
|
await artifacts.reply(`${lead}
|
|
43166
43299
|
\u{1F4CE} ${name} (${size} bytes)${where}`);
|
|
43167
|
-
|
|
43300
|
+
const linked = `attached ${name} (${size} bytes) to the run page as ${key}; this conversation's channel takes no file uploads, so the lead and the file's link were posted instead`;
|
|
43301
|
+
return publishForPr ? `error: ${linked}; PR publication requires a completed channel upload \u2014 no public PR image was published` : linked;
|
|
43168
43302
|
}
|
|
43169
43303
|
const kept = "the file is kept on the run page";
|
|
43170
43304
|
let ticket;
|
|
@@ -43182,7 +43316,7 @@ async function attachThroughStore(path, name, lead, artifacts, ctx) {
|
|
|
43182
43316
|
} catch (err2) {
|
|
43183
43317
|
return `error: the channel refused to complete the upload of ${name}: ${describe4(err2)}; ${kept}`;
|
|
43184
43318
|
}
|
|
43185
|
-
return `attached ${name} (${size} bytes) to the conversation and the run page
|
|
43319
|
+
return finish2(`attached ${name} (${size} bytes) to the conversation and the run page`);
|
|
43186
43320
|
}
|
|
43187
43321
|
var MAX_ARTIFACT_BYTES, TRANSFER_FLOOR_BYTES_PER_MS, TRANSFER_SETUP_MS, STAT_TIMEOUT_MS, describe4, secs2, PUT_REPORT_FORMAT, attachFileTool;
|
|
43188
43322
|
var init_attach = __esm({
|
|
@@ -43190,6 +43324,7 @@ var init_attach = __esm({
|
|
|
43190
43324
|
"use strict";
|
|
43191
43325
|
init_contentType();
|
|
43192
43326
|
init_keys();
|
|
43327
|
+
init_prImages();
|
|
43193
43328
|
init_runEvents();
|
|
43194
43329
|
init_bashTimeout();
|
|
43195
43330
|
init_binaryRead();
|
|
@@ -43204,11 +43339,15 @@ var init_attach = __esm({
|
|
|
43204
43339
|
attachFileTool = {
|
|
43205
43340
|
name: "attach_file",
|
|
43206
43341
|
failsInText: true,
|
|
43207
|
-
description: `Post a file from the workspace into the conversation so the person sees it inline \u2014 a screenshot (e.g. from \`playwright screenshot\`), a rendered PDF, a recording, a log. Use it whenever you produce an image worth showing: a link to a file is not a picture. Whole files only: up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) where the artifact store is configured, ${MAX_READ_BYTES} bytes otherwise \u2014 the result says which.`,
|
|
43342
|
+
description: `Post a file from the workspace into the conversation so the person sees it inline \u2014 a screenshot (e.g. from \`playwright screenshot\`), a rendered PDF, a recording, a log. Use it whenever you produce an image worth showing: a link to a file is not a picture. Whole files only: up to ${MAX_ARTIFACT_BYTES} bytes (1 GiB) where the artifact store is configured, ${MAX_READ_BYTES} bytes otherwise \u2014 the result says which. For an inspected, non-sensitive PNG screenshot, publishForPr: true also deliberately publishes a public PR image URL after thread delivery (10 MiB max, artifact retention applies). Omit it to keep the file private.`,
|
|
43208
43343
|
inputSchema: {
|
|
43209
43344
|
type: "object",
|
|
43210
43345
|
properties: {
|
|
43211
43346
|
path: { type: "string", description: "Relative path of the file in the workspace" },
|
|
43347
|
+
publishForPr: {
|
|
43348
|
+
type: "boolean",
|
|
43349
|
+
description: "Explicitly make this PNG public for embedding in a PR, after attaching it here. Default false; inspect/redact first. Requires a bound permitted repository and artifact store. The copy expires under the artifact lifecycle."
|
|
43350
|
+
},
|
|
43212
43351
|
comment: {
|
|
43213
43352
|
type: "string",
|
|
43214
43353
|
description: "One line posted with the file saying what it shows (default: the file name)"
|
|
@@ -43221,7 +43360,16 @@ var init_attach = __esm({
|
|
|
43221
43360
|
if (!path) return "error: path is required";
|
|
43222
43361
|
const name = fileNameOf2(path);
|
|
43223
43362
|
const lead = typeof input.comment === "string" && input.comment.trim() ? input.comment.trim() : name;
|
|
43224
|
-
if (
|
|
43363
|
+
if (input.publishForPr !== void 0 && typeof input.publishForPr !== "boolean")
|
|
43364
|
+
return "error: publishForPr must be a boolean";
|
|
43365
|
+
const publishForPr = input.publishForPr === true;
|
|
43366
|
+
if (publishForPr) {
|
|
43367
|
+
if (!ctx.repo) return "error: PR publication requires a bound repository";
|
|
43368
|
+
if (!ctx.github?.canWrite(ctx.repo)) return "error: PR publication requires permission to use this repository";
|
|
43369
|
+
if (!ctx.artifacts) return "error: PR publication requires the artifact store; no public image was published";
|
|
43370
|
+
if (contentTypeFor(name) !== "image/png") return "error: PR publication supports only PNG screenshots";
|
|
43371
|
+
}
|
|
43372
|
+
if (ctx.artifacts) return attachThroughStore(path, name, lead, ctx.artifacts, ctx, publishForPr);
|
|
43225
43373
|
if (!ctx.attach) {
|
|
43226
43374
|
return "attach_file is not available here: this conversation's channel takes no file uploads \u2014 link to the file instead";
|
|
43227
43375
|
}
|
|
@@ -49861,6 +50009,7 @@ function visiblePart(part) {
|
|
|
49861
50009
|
}
|
|
49862
50010
|
function fromPiMessage(message4, provider, tools) {
|
|
49863
50011
|
if (message4.stopReason === "error" || message4.stopReason === "aborted") {
|
|
50012
|
+
if (message4.stopReason === "aborted") throw new DOMException("The model call was aborted", "AbortError");
|
|
49864
50013
|
const raw = message4.errorMessage ?? "the model call failed";
|
|
49865
50014
|
const failure2 = classifyProviderFailure({ error: raw, provider, model: message4.model });
|
|
49866
50015
|
const schemaRejection = failure2.cause === "request-rejected" && failure2.status === 400 ? providerSchemaRejectionOf(raw, tools) : void 0;
|
|
@@ -49992,6 +50141,7 @@ var init_piAi = __esm({
|
|
|
49992
50141
|
const message4 = await this.api$().stream(model, shaped.body, options).result();
|
|
49993
50142
|
return fromPiMessage(message4, this.name, req.tools);
|
|
49994
50143
|
} catch (err2) {
|
|
50144
|
+
if (err2 instanceof Error && err2.name === "AbortError") throw err2;
|
|
49995
50145
|
const failure2 = providerFailureOf(err2);
|
|
49996
50146
|
const schemaRejection = failure2.schemaRejection ?? (failure2.cause === "request-rejected" && failure2.status === 400 ? providerSchemaRejectionOf(err2, req.tools) : void 0);
|
|
49997
50147
|
throw new ProviderFailure(failure2.cause, {
|
|
@@ -50154,10 +50304,12 @@ function judgePush(tail, ctx) {
|
|
|
50154
50304
|
return refused4("repo:use \u2014 existing-PR publication requires the explicit owned destination");
|
|
50155
50305
|
if (additionalRefspecs.length > 0)
|
|
50156
50306
|
return refused4("repo:use \u2014 existing-PR publication allows exactly one owned destination");
|
|
50157
|
-
const destination2 = refspec.replace(/^\+/, "").split(":")
|
|
50158
|
-
const branch3 = destination2
|
|
50307
|
+
const [source3, destination2 = source3] = refspec.replace(/^\+/, "").split(":");
|
|
50308
|
+
const branch3 = destination2.replace(/^refs\/heads\//, "");
|
|
50159
50309
|
if (branch3 !== publication.ref)
|
|
50160
50310
|
return refused4(`repo:use \u2014 push to \`${branch3}\`, not the owned publication ref ${publication.ref}`);
|
|
50311
|
+
if (source3.replace(/^refs\/heads\//, "") !== publication.ref)
|
|
50312
|
+
return refused4(`repo:use \u2014 push from the owned publication ref ${publication.ref}; the checkout may have moved`);
|
|
50161
50313
|
const lease = `--force-with-lease=refs/heads/${publication.ref}:${publication.expectedHeadSha}`;
|
|
50162
50314
|
if (!tail.split(/\s+/).includes(lease))
|
|
50163
50315
|
return refused4(
|
|
@@ -50165,11 +50317,19 @@ function judgePush(tail, ctx) {
|
|
|
50165
50317
|
);
|
|
50166
50318
|
return allowed;
|
|
50167
50319
|
}
|
|
50320
|
+
if (ctx.branch !== void 0 && refspec === void 0)
|
|
50321
|
+
return refused4(
|
|
50322
|
+
`repo:use \u2014 name the run's branch ${ctx.branch} as the push source and destination; the checkout may have moved`
|
|
50323
|
+
);
|
|
50324
|
+
if (ctx.branch !== void 0 && additionalRefspecs.length > 0)
|
|
50325
|
+
return refused4("repo:use \u2014 a bound run may push exactly one branch");
|
|
50168
50326
|
if (refspec === void 0) return allowed;
|
|
50169
|
-
const destination = refspec.replace(/^\+/, "").split(":")
|
|
50327
|
+
const [source2, destination = source2] = refspec.replace(/^\+/, "").split(":");
|
|
50170
50328
|
if (ctx.branch !== void 0) {
|
|
50171
|
-
const branch3 = destination
|
|
50329
|
+
const branch3 = destination.replace(/^refs\/heads\//, "");
|
|
50172
50330
|
if (branch3 !== ctx.branch) return refused4(`repo:use \u2014 push to \`${branch3}\`, not the run's branch ${ctx.branch}`);
|
|
50331
|
+
if (source2.replace(/^refs\/heads\//, "") !== ctx.branch)
|
|
50332
|
+
return refused4(`repo:use \u2014 push from the run's branch ${ctx.branch}; the checkout may have moved`);
|
|
50173
50333
|
return allowed;
|
|
50174
50334
|
}
|
|
50175
50335
|
const branch2 = destination.replace(/^refs\/heads\//, "");
|
|
@@ -52391,7 +52551,7 @@ async function observeCodingWorkspace(executor, opts, span) {
|
|
|
52391
52551
|
}
|
|
52392
52552
|
return {
|
|
52393
52553
|
observation: {
|
|
52394
|
-
head: pushed === void 0 ? headSha : parseRevParseOutput(tipOut),
|
|
52554
|
+
head: pushed === void 0 ? headSha : parseRevParseOutput(tipOut) ?? (checkedOut === pushed ? headSha : void 0),
|
|
52395
52555
|
branch: branch2,
|
|
52396
52556
|
checkedOut,
|
|
52397
52557
|
remoteHead,
|
|
@@ -52408,7 +52568,11 @@ async function observeCodingWorkspace(executor, opts, span) {
|
|
|
52408
52568
|
return (await probesAt(`git -C ${shellQuote(dir)}`)).observation;
|
|
52409
52569
|
}
|
|
52410
52570
|
function salvageTargetOf(opts) {
|
|
52411
|
-
|
|
52571
|
+
if (opts.ownedBranch !== void 0 && opts.checkedOut !== opts.ownedBranch)
|
|
52572
|
+
return {
|
|
52573
|
+
skipped: `the ending checkpoint kept work on \`${opts.checkedOut ?? "an unreadable checkout"}\`: the owned branch is \`${opts.ownedBranch}\``
|
|
52574
|
+
};
|
|
52575
|
+
const branch2 = opts.ownedBranch ?? opts.pushedBranch ?? opts.checkedOut;
|
|
52412
52576
|
if (branch2 === void 0)
|
|
52413
52577
|
return {
|
|
52414
52578
|
skipped: "the budget-end salvage was skipped: the workspace's branch could not be read, so there is nowhere to push"
|
|
@@ -53457,8 +53621,9 @@ async function runLoop(deps, ctx) {
|
|
|
53457
53621
|
let workspaceObserved = false;
|
|
53458
53622
|
let workSalvageAttempted = false;
|
|
53459
53623
|
let endingSalvageAttempted = false;
|
|
53624
|
+
const coordinatorBranch = coordinator?.publication?.publicationRef ?? (coordinator !== void 0 ? binding?.ref ?? repoCtx.ref : void 0);
|
|
53460
53625
|
const observeWorkspaceNow = async () => {
|
|
53461
|
-
const pushedBranch = pushes.branch();
|
|
53626
|
+
const pushedBranch = coordinator !== void 0 ? coordinatorBranch : pushes.branch();
|
|
53462
53627
|
const observed = await root.span(
|
|
53463
53628
|
"run.observe_workspace",
|
|
53464
53629
|
(span) => observeCodingWorkspace(
|
|
@@ -53482,9 +53647,10 @@ async function runLoop(deps, ctx) {
|
|
|
53482
53647
|
const preserveCodingChildWork = async (cue) => {
|
|
53483
53648
|
if (workSalvageAttempted || !isCodingPrRun || coordinator === void 0) return;
|
|
53484
53649
|
if (!workspaceObserved) await observeWorkspaceNow();
|
|
53485
|
-
const target = salvageTargetOf({
|
|
53650
|
+
const target = coordinatorBranch === void 0 ? { skipped: "the ending checkpoint was skipped: the unit's branch is unavailable" } : salvageTargetOf({
|
|
53486
53651
|
pushedBranch: pushes.branch(),
|
|
53487
53652
|
checkedOut: observedCheckedOut,
|
|
53653
|
+
ownedBranch: coordinatorBranch,
|
|
53488
53654
|
base: repoCtx.baseRef ?? await coordinatorBase
|
|
53489
53655
|
});
|
|
53490
53656
|
const salvaged = "skipped" in target ? { pushed: false, summary: target.skipped } : await root.span(
|
|
@@ -53504,7 +53670,7 @@ async function runLoop(deps, ctx) {
|
|
|
53504
53670
|
const cleanCompletion = cue === "completion" && !salvaged.pushed && (/ended with nothing to preserve/i.test(salvaged.summary) || "skipped" in target && (observedUncommitted ?? 0) === 0 && (observedUnpushed ?? 0) === 0);
|
|
53505
53671
|
if (cleanCompletion) return;
|
|
53506
53672
|
workSalvageAttempted = true;
|
|
53507
|
-
if (cue === "ending" || cue === "completion") endingSalvageAttempted = true;
|
|
53673
|
+
if (cue === "ending" || cue === "completion" && !("skipped" in target)) endingSalvageAttempted = true;
|
|
53508
53674
|
onEvent({
|
|
53509
53675
|
type: "run_note",
|
|
53510
53676
|
kind: cue === "budget" ? "budget_salvage" : "work_salvage",
|
|
@@ -65043,14 +65209,13 @@ async function askModel(input, deps) {
|
|
|
65043
65209
|
);
|
|
65044
65210
|
return { ...seam.value, attempts: seam.attempts };
|
|
65045
65211
|
} catch (err2) {
|
|
65046
|
-
const timedOut = err2 instanceof Error && (err2.name === "TimeoutError" || err2.name === "AbortError");
|
|
65212
|
+
const timedOut = typedProviderFailureOf(err2) === void 0 && err2 instanceof Error && (err2.name === "TimeoutError" || err2.name === "AbortError");
|
|
65047
65213
|
const attempts = attemptsOfThrow(err2);
|
|
65048
65214
|
if (timedOut) {
|
|
65049
65215
|
return {
|
|
65050
65216
|
verdict: "silent",
|
|
65051
|
-
reason:
|
|
65217
|
+
reason: "The request timed out before it could start.",
|
|
65052
65218
|
source: "timeout",
|
|
65053
|
-
providerFailure: "transient",
|
|
65054
65219
|
...attempts ? { attempts } : {}
|
|
65055
65220
|
};
|
|
65056
65221
|
}
|
|
@@ -73822,28 +73987,49 @@ async function steerChild(body, deps) {
|
|
|
73822
73987
|
const steered = await deps.steerChild?.(body.runId, text, instance) ?? false;
|
|
73823
73988
|
return json3(200, { ok: true, outcome: steered ? "steered" : "not_live", at });
|
|
73824
73989
|
}
|
|
73990
|
+
function publicationStepPrefix(row2) {
|
|
73991
|
+
const segment = row2.segments?.reduce((latest, entry) => Math.max(latest, entry.index), 1) ?? 1;
|
|
73992
|
+
if ((row2.segments?.filter((entry) => entry.index === segment).length ?? 0) > 1)
|
|
73993
|
+
throw new PublicationBindingRefusal("publication_facts_mismatch");
|
|
73994
|
+
const base = stepPrefixOf(row2.unit, segment > 1 ? { segment, renewalsSpent: 0, spendUsd: null } : void 0);
|
|
73995
|
+
const resumes = Object.entries(row2.wakes ?? {}).filter(
|
|
73996
|
+
([, answer]) => answer.kind === "segment" && answer.index === segment && answer.leaseMs !== void 0
|
|
73997
|
+
);
|
|
73998
|
+
let prefix = base;
|
|
73999
|
+
for (let attempt = 1; attempt <= resumes.length; attempt += 1) {
|
|
74000
|
+
const idlePrefix = `${prefix}/idle/`;
|
|
74001
|
+
const answers = resumes.filter(
|
|
74002
|
+
([key]) => key.startsWith(idlePrefix) && /^[1-9][0-9]*$/.test(key.slice(idlePrefix.length))
|
|
74003
|
+
);
|
|
74004
|
+
if (answers.length !== 1) throw new PublicationBindingRefusal("publication_facts_mismatch");
|
|
74005
|
+
prefix = `${base}/r${attempt}`;
|
|
74006
|
+
}
|
|
74007
|
+
return prefix;
|
|
74008
|
+
}
|
|
73825
74009
|
async function advanceFindingsPublication(deps, instance, row2, facts2, runId3) {
|
|
73826
74010
|
if (row2.publication === void 0 || facts2.headSha === row2.publication.expectedHeadSha) return row2;
|
|
73827
74011
|
const child = await deps.runs.getRun(runId3, { include: "messages" });
|
|
73828
74012
|
const owner = { instanceId: instance.id, unit: row2.unit };
|
|
73829
74013
|
const latestReview = [...row2.rounds].reverse().find((round2) => round2.agent === "review");
|
|
73830
|
-
const
|
|
73831
|
-
const prefix = row2.recovery !== void 0 ? `${row2.unit}/recovery` : stepPrefixOf(row2.unit, segment > 1 ? { segment, renewalsSpent: 0, spendUsd: null } : void 0);
|
|
74014
|
+
const prefix = row2.recovery !== void 0 ? `${row2.unit}/recovery` : publicationStepPrefix(row2);
|
|
73832
74015
|
const findingsPrefix = `${instance.id}:${prefix}/${latestReview?.index}/findings`;
|
|
73833
74016
|
if (row2.recovery === void 0) {
|
|
73834
74017
|
const tags = child.ok ? child.value.events?.filter((event) => event.type === "coordinator_tag") ?? [] : [];
|
|
73835
74018
|
const tag = tags.length === 1 ? tags[0] : void 0;
|
|
73836
|
-
if (row2.ending !== void 0 || row2.idle !== void 0 || latestReview?.outcome !== "request_changes" || !child.ok || child.value.startedAt < latestReview.at || tag?.parentInstanceId !== instance.id || tag.unit !== row2.unit || tag.base !== instance.base ||
|
|
74019
|
+
if (row2.ending !== void 0 || row2.idle !== void 0 || latestReview?.outcome !== "request_changes" || !child.ok || child.value.startedAt < latestReview.at || tag?.parentInstanceId !== instance.id || tag.unit !== row2.unit || tag.base !== instance.base || !samePublicationBinding(tag.publication, row2.publication))
|
|
73837
74020
|
throw new PublicationBindingRefusal("publication_facts_mismatch");
|
|
73838
74021
|
const listing = await deps.runs.listRuns({
|
|
73839
|
-
status: "
|
|
74022
|
+
status: "all",
|
|
73840
74023
|
visibleTo: EVERY_RUN,
|
|
73841
74024
|
threadKey: row2.threadKey ?? instance.threadKey,
|
|
73842
74025
|
limit: RUN_LIST_MAX_LIMIT
|
|
73843
74026
|
});
|
|
73844
|
-
|
|
74027
|
+
const attempts = listing.runs.filter(
|
|
73845
74028
|
(run2) => run2.parentInstanceId === instance.id && isStepAttempt(run2.idempotencyKey, findingsPrefix)
|
|
73846
|
-
)
|
|
74029
|
+
);
|
|
74030
|
+
if (listing.storeUnavailable || listing.nextBefore !== void 0 || attempts.filter((run2) => run2.id === runId3).length !== 1 || attempts.some(
|
|
74031
|
+
(run2) => run2.id !== runId3 && !(run2.finished && (run2.status === "failed" || run2.status === "interrupted") && run2.agent === "coding" && run2.userId === instance.userId && run2.repo?.toLowerCase() === instance.repo.toLowerCase() && run2.threadKey === (row2.threadKey ?? instance.threadKey) && run2.startedAt >= latestReview.at && run2.finishedAt !== void 0 && run2.finishedAt >= run2.startedAt && run2.finishedAt <= child.value.startedAt && (run2.pushed?.length ?? 0) === 0)
|
|
74032
|
+
))
|
|
73847
74033
|
throw new PublicationBindingRefusal("publication_facts_mismatch");
|
|
73848
74034
|
}
|
|
73849
74035
|
if (!child.ok || child.value.finished !== true || child.value.status !== "completed" || child.value.agent !== "coding" || child.value.parentInstanceId !== instance.id || child.value.userId !== instance.userId || child.value.repo?.toLowerCase() !== instance.repo.toLowerCase() || child.value.threadKey !== (row2.threadKey ?? instance.threadKey) || (row2.recovery !== void 0 ? child.value.idempotencyKey?.startsWith(`${instance.id}:${row2.unit}/recovery/`) !== true || !/\/findings(?:\/a[1-9][0-9]*)?$/.test(child.value.idempotencyKey ?? "") : !isStepAttempt(child.value.idempotencyKey, findingsPrefix)) || !fullHead(facts2.headSha) || child.value.headSha !== facts2.headSha || child.value.pushed?.some((push) => push.ref === row2.branch && push.sha === facts2.headSha) !== true || facts2.state !== "open" || facts2.sameRepoHead !== true || facts2.headBranchExists !== true || facts2.headRef !== row2.publication.headRef || facts2.baseRef !== row2.publication.baseRef || facts2.verifiedHead?.repo.toLowerCase() !== instance.repo.toLowerCase() || facts2.verifiedHead.ref !== row2.publication.publicationRef || facts2.verifiedHead.sha !== facts2.headSha || row2.publication.repo.toLowerCase() !== instance.repo.toLowerCase() || row2.publication.pr !== row2.pr?.number || row2.publication.baseRef !== instance.base || row2.publication.headRef !== row2.branch || row2.publication.publicationRef !== row2.branch || !samePublicationOwner(row2.publication.owner, owner))
|
|
@@ -74060,13 +74246,13 @@ async function recoverOriginalUnit(body, deps, caller) {
|
|
|
74060
74246
|
const reviewThreadKey = row2.reviewThread?.threadKey ?? row2.threadKey ?? instance.threadKey;
|
|
74061
74247
|
const unitThreadKey = row2.threadKey ?? instance.threadKey;
|
|
74062
74248
|
const listing = await deps.runs.listRuns({
|
|
74063
|
-
status:
|
|
74249
|
+
status: "all",
|
|
74064
74250
|
visibleTo: EVERY_RUN,
|
|
74065
74251
|
threadKey: reviewThreadKey,
|
|
74066
74252
|
limit: RUN_LIST_MAX_LIMIT
|
|
74067
74253
|
});
|
|
74068
74254
|
const unitListing = unitThreadKey === reviewThreadKey ? listing : await deps.runs.listRuns({
|
|
74069
|
-
status:
|
|
74255
|
+
status: "all",
|
|
74070
74256
|
visibleTo: EVERY_RUN,
|
|
74071
74257
|
threadKey: unitThreadKey,
|
|
74072
74258
|
limit: RUN_LIST_MAX_LIMIT
|
|
@@ -74129,17 +74315,28 @@ async function recoverOriginalUnit(body, deps, caller) {
|
|
|
74129
74315
|
const verified2 = facts2.verifiedHead;
|
|
74130
74316
|
if (facts2.state !== "open" || facts2.sameRepoHead !== true || facts2.headBranchExists !== true || facts2.headRef !== row2.branch || facts2.baseRef !== base || !fullHead(facts2.headSha) || verified2 === void 0 || verified2.repo.toLowerCase() !== instance.repo.toLowerCase() || verified2.ref !== row2.branch || verified2.sha !== facts2.headSha)
|
|
74131
74317
|
return json3(409, { ok: false, error: "recovery_facts_mismatch", at });
|
|
74132
|
-
const findingsStep = `${segmentPrefix}/${boundary.index}/findings
|
|
74133
|
-
const findingsPrefix = `${instance.id}:${
|
|
74318
|
+
const findingsStep = `${segmentPrefix}/${boundary.index}/findings`;
|
|
74319
|
+
const findingsPrefix = `${instance.id}:${findingsStep}`;
|
|
74134
74320
|
const headMoved = facts2.headSha !== reviewedHead || expectedHead !== reviewedHead;
|
|
74135
|
-
const
|
|
74136
|
-
|
|
74137
|
-
|
|
74138
|
-
|
|
74139
|
-
|
|
74140
|
-
|
|
74141
|
-
|
|
74142
|
-
|
|
74321
|
+
const failedStep = row2.ending.step;
|
|
74322
|
+
if (row2.ending.cause === "step_threw" && failedStep?.includes("/pr-check") === true) {
|
|
74323
|
+
const checkedStep = failedStep.slice(0, -"/pr-check".length);
|
|
74324
|
+
if (!failedStep.endsWith("/pr-check") || boundary.outcome !== "request_changes" || !isStepAttempt(checkedStep, findingsStep) || row2.ending.round !== boundary.index)
|
|
74325
|
+
return json3(409, { ok: false, error: "recovery_head_moved", at });
|
|
74326
|
+
const attempts = unitListing.runs.filter(
|
|
74327
|
+
(run2) => run2.parentInstanceId === instance.id && isStepAttempt(run2.idempotencyKey, findingsPrefix)
|
|
74328
|
+
);
|
|
74329
|
+
const checkedAttempts = attempts.filter((run2) => run2.idempotencyKey === `${instance.id}:${checkedStep}`);
|
|
74330
|
+
const completed = checkedAttempts.length === 1 ? checkedAttempts[0] : void 0;
|
|
74331
|
+
const reviewFinishedAt = review.finishedAt;
|
|
74332
|
+
if (reviewFinishedAt === void 0 || completed === void 0 || !completed.finished || completed.status !== "completed" || completed.agent !== "coding" || completed.userId !== instance.userId || completed.repo?.toLowerCase() !== instance.repo.toLowerCase() || completed.threadKey !== unitThreadKey || completed.pr !== void 0 && (completed.pr.number !== pr.number || completed.pr.head !== void 0 && completed.pr.head !== row2.branch) || completed.headSha !== facts2.headSha || expectedHead !== reviewedHead && expectedHead !== facts2.headSha || completed.pushed?.some((push) => push.ref !== row2.branch) === true || headMoved && completed.pushed?.some((push) => push.ref === row2.branch && push.sha === facts2.headSha) !== true || completed.startedAt < reviewFinishedAt || completed.finishedAt === void 0 || completed.finishedAt < completed.startedAt || completed.finishedAt > row2.ending.at || // Inspect every attempt, not just runs matching the current PR head:
|
|
74333
|
+
// any competing success or push remains ambiguous after a later push.
|
|
74334
|
+
attempts.some(
|
|
74335
|
+
(run2) => run2.id !== completed.id && !(run2.finished && (run2.status === "failed" || run2.status === "interrupted") && run2.agent === "coding" && run2.userId === instance.userId && run2.repo?.toLowerCase() === instance.repo.toLowerCase() && run2.threadKey === unitThreadKey && run2.startedAt >= reviewFinishedAt && run2.finishedAt !== void 0 && run2.finishedAt >= run2.startedAt && run2.finishedAt <= completed.startedAt && (run2.pushed?.length ?? 0) === 0)
|
|
74336
|
+
))
|
|
74337
|
+
return json3(409, { ok: false, error: "recovery_head_moved", at });
|
|
74338
|
+
findingsRunId = completed.id;
|
|
74339
|
+
findingsKey = completed.idempotencyKey;
|
|
74143
74340
|
expectedHead = facts2.headSha;
|
|
74144
74341
|
kind = "review";
|
|
74145
74342
|
round2 = boundary.index + 1;
|
|
@@ -74148,7 +74345,10 @@ async function recoverOriginalUnit(body, deps, caller) {
|
|
|
74148
74345
|
lastPush: expectedHead,
|
|
74149
74346
|
publication: { ...publication, expectedHeadSha: expectedHead }
|
|
74150
74347
|
};
|
|
74151
|
-
} else
|
|
74348
|
+
} else {
|
|
74349
|
+
if (headMoved) return json3(409, { ok: false, error: "recovery_head_moved", at });
|
|
74350
|
+
expectedHead = reviewedHead;
|
|
74351
|
+
}
|
|
74152
74352
|
const floor = minutesToMs(leaseMinimum(kind === "findings" ? "fix" : "review"));
|
|
74153
74353
|
if (!Number.isFinite(remainingMs) || remainingMs < floor)
|
|
74154
74354
|
return json3(409, { ok: false, error: "recovery_wall_clock_exhausted", at });
|
|
@@ -75787,7 +75987,7 @@ function createAdminCoordinatorHandler(deps) {
|
|
|
75787
75987
|
})();
|
|
75788
75988
|
};
|
|
75789
75989
|
}
|
|
75790
|
-
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json3, describe12, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, githubSecondStart, CHILD_SUPERSESSION_REASONS, PublicationBindingRefusal, samePublicationOwner, fullHead, isStepAttempt, ROUND_OUTCOMES, ENDING_ICON, STEPS;
|
|
75990
|
+
var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json3, describe12, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, githubSecondStart, CHILD_SUPERSESSION_REASONS, PublicationBindingRefusal, samePublicationOwner, samePublicationBinding, fullHead, isStepAttempt, ROUND_OUTCOMES, ENDING_ICON, STEPS;
|
|
75791
75991
|
var init_adminCoordinator = __esm({
|
|
75792
75992
|
"../../src/channels/adminCoordinator.ts"() {
|
|
75793
75993
|
"use strict";
|
|
@@ -75851,6 +76051,7 @@ var init_adminCoordinator = __esm({
|
|
|
75851
76051
|
reason;
|
|
75852
76052
|
};
|
|
75853
76053
|
samePublicationOwner = (left, right) => left?.instanceId === right.instanceId && left.unit === right.unit;
|
|
76054
|
+
samePublicationBinding = (left, right) => left !== void 0 && left.repo === right.repo && left.pr === right.pr && left.headRef === right.headRef && left.baseRef === right.baseRef && left.expectedHeadSha === right.expectedHeadSha && left.publicationRef === right.publicationRef && samePublicationOwner(left.owner, right.owner);
|
|
75854
76055
|
fullHead = (value) => typeof value === "string" && /^[0-9a-f]{40}$/i.test(value);
|
|
75855
76056
|
isStepAttempt = (key, step) => key === step || key?.startsWith(`${step}/a`) === true && /^[1-9][0-9]*$/.test(key.slice(step.length + 2));
|
|
75856
76057
|
ROUND_OUTCOMES = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreplane/switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.263.0",
|
|
4
4
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://openswitchboard.dev",
|